Coverage Report

Created: 2026-08-07 16:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\cssh-rs\cssh-rs\cssh-rs-platform-windows\src\api.rs
Line
Count
Source
1
//! Windows API abstraction layer for console and system operations.
2
//!
3
//! This module provides a trait-based abstraction over Windows APIs to enable
4
//! mocking in tests and centralize Windows-specific functionality.
5
6
use log::{error, warn};
7
use std::ffi::{OsStr, OsString};
8
use std::os::windows::ffi::OsStrExt;
9
use std::{mem, ptr};
10
11
use windows::core::{BOOL, HSTRING, PCWSTR};
12
use windows::Win32::Foundation::{COLORREF, FALSE, HANDLE, HWND, LPARAM, TRUE};
13
use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_BORDER_COLOR};
14
use windows::Win32::Graphics::Gdi::InvalidateRect;
15
use windows::Win32::System::Console::{
16
    FillConsoleOutputAttribute, GetConsoleProcessList, GetConsoleScreenBufferInfo,
17
    GetConsoleWindow, GetStdHandle, ReadConsoleInputW, SetConsoleCtrlHandler,
18
    SetConsoleTextAttribute, CONSOLE_CHARACTER_ATTRIBUTES, CONSOLE_SCREEN_BUFFER_INFO, COORD,
19
    CTRL_BREAK_EVENT, CTRL_C_EVENT, INPUT_RECORD, INPUT_RECORD_0, STD_HANDLE, STD_INPUT_HANDLE,
20
    STD_OUTPUT_HANDLE,
21
};
22
use windows::Win32::System::Console::{GetConsoleMode, SetConsoleMode, CONSOLE_MODE};
23
use windows::Win32::System::Console::{
24
    ScrollConsoleScreenBufferW, SetConsoleCursorPosition, CHAR_INFO, KEY_EVENT as KEY_EVENT_U32,
25
    SMALL_RECT,
26
};
27
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
28
use windows::Win32::System::Threading::{
29
    CreateProcessW, CREATE_NEW_CONSOLE, PROCESS_INFORMATION, STARTF_USESHOWWINDOW, STARTUPINFOW,
30
};
31
use windows::Win32::System::Threading::{GetExitCodeProcess, OpenProcess};
32
use windows::Win32::UI::WindowsAndMessaging::{
33
    BringWindowToTop, EnumWindows, GetForegroundWindow, GetWindowPlacement, GetWindowTextW,
34
    GetWindowThreadProcessId, MoveWindow, SetWindowPos, SetWindowTextW, ShowWindow, HWND_NOTOPMOST,
35
    HWND_TOPMOST, SHOW_WINDOW_CMD, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SW_SHOWNOACTIVATE,
36
    SYSTEM_METRICS_INDEX, WINDOWPLACEMENT,
37
};
38
39
#[cfg(any(test, feature = "mock"))]
40
use mockall::automock;
41
42
use crate::MAX_WINDOW_TITLE_LENGTH;
43
44
/// Trait for Windows API operations to enable mocking in tests.
45
///
46
/// This trait abstracts Windows API calls to allow for unit testing without
47
/// actual system interaction. All console and system operations should go
48
/// through this trait.
49
#[cfg_attr(any(test, feature = "mock"), automock)]
50
pub trait WindowsApi: Send + Sync {
51
    /// Sets the console window title.
52
    ///
53
    /// # Arguments
54
    ///
55
    /// * `title` - The string to be set as window title
56
    ///
57
    /// # Returns
58
    ///
59
    /// Result indicating success or failure of the operation
60
    fn set_console_title(&self, title: &str) -> windows::core::Result<()>;
61
62
    /// Gets the console window title as UTF-16 buffer.
63
    ///
64
    /// # Arguments
65
    ///
66
    /// * `buffer` - Mutable buffer to store the UTF-16 encoded title
67
    ///
68
    /// # Returns
69
    ///
70
    /// Number of characters copied to the buffer
71
    fn get_console_title(&self, buffer: &mut [u16]) -> i32;
72
73
    /// Gets OS version string.
74
    ///
75
    /// # Returns
76
    ///
77
    /// String representation of the OS version
78
    fn get_os_version(&self) -> String;
79
80
    /// Arranges the console window position and size.
81
    ///
82
    /// # Arguments
83
    ///
84
    /// * `x` - The x coordinate to move the window to
85
    /// * `y` - The y coordinate to move the window to
86
    /// * `width` - The width in pixels to resize the window to
87
    /// * `height` - The height in pixels to resize the window to
88
    ///
89
    /// # Returns
90
    ///
91
    /// Result indicating success or failure of the operation
92
    fn arrange_console(&self, x: i32, y: i32, width: i32, height: i32)
93
        -> windows::core::Result<()>;
94
95
    /// Sets console text attribute.
96
    ///
97
    /// # Arguments
98
    ///
99
    /// * `attributes` - Console character attributes to set
100
    ///
101
    /// # Returns
102
    ///
103
    /// Result indicating success or failure of the operation
104
    fn set_console_text_attribute(
105
        &self,
106
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
107
    ) -> windows::core::Result<()>;
108
109
    /// Gets console screen buffer info.
110
    ///
111
    /// # Returns
112
    ///
113
    /// Console screen buffer information or error
114
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO>;
115
116
    /// Fills console output with specified attribute.
117
    ///
118
    /// # Arguments
119
    ///
120
    /// * `attribute` - Attribute to fill with
121
    /// * `length` - Number of characters to fill
122
    /// * `coord` - Starting coordinate
123
    ///
124
    /// # Returns
125
    ///
126
    /// Number of characters actually filled or error
127
    fn fill_console_output_attribute(
128
        &self,
129
        attribute: u16,
130
        length: u32,
131
        coord: COORD,
132
    ) -> windows::core::Result<u32>;
133
134
    /// Scrolls console screen buffer.
135
    ///
136
    /// # Arguments
137
    ///
138
    /// * `scroll_rect` - Rectangle to scroll
139
    /// * `scroll_target` - Target coordinate for scrolling
140
    /// * `fill_char` - Character to fill empty space with
141
    ///
142
    /// # Returns
143
    ///
144
    /// Result indicating success or failure of the operation
145
    fn scroll_console_screen_buffer(
146
        &self,
147
        scroll_rect: SMALL_RECT,
148
        scroll_target: COORD,
149
        fill_char: CHAR_INFO,
150
    ) -> windows::core::Result<()>;
151
152
    /// Sets console cursor position.
153
    ///
154
    /// # Arguments
155
    ///
156
    /// * `position` - New cursor position
157
    ///
158
    /// # Returns
159
    ///
160
    /// Result indicating success or failure of the operation
161
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()>;
162
163
    /// Gets standard handle.
164
    ///
165
    /// # Arguments
166
    ///
167
    /// * `handle_type` - Type of standard handle to retrieve
168
    ///
169
    /// # Returns
170
    ///
171
    /// Handle to the requested standard device or error
172
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE>;
173
174
    /// Reads console input.
175
    ///
176
    /// # Arguments
177
    ///
178
    /// * `buffer` - Buffer to store input records
179
    ///
180
    /// # Returns
181
    ///
182
    /// Number of records read or error
183
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32>;
184
185
    /// Sets DWM window attribute for border color.
186
    ///
187
    /// # Arguments
188
    ///
189
    /// * `color` - Color to set as border color
190
    ///
191
    /// # Returns
192
    ///
193
    /// Result indicating success or failure of the operation
194
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()>;
195
196
    /// Marks the entire console window client area as needing a redraw.
197
    ///
198
    /// Used to nudge the legacy Win10 conhost into repainting from its
199
    /// own buffer state after bulk attribute changes that the renderer
200
    /// otherwise leaves stale on the trailing row/column.
201
    ///
202
    /// # Returns
203
    ///
204
    /// Result indicating success or failure of the operation
205
    fn invalidate_console_window(&self) -> windows::core::Result<()>;
206
207
    /// Writes input records to the console input buffer.
208
    ///
209
    /// # Arguments
210
    ///
211
    /// * `buffer` - Input records to write
212
    /// * `number_written` - Mutable reference to store number of records written
213
    ///
214
    /// # Returns
215
    ///
216
    /// Result indicating success or failure of the operation
217
    fn write_console_input(
218
        &self,
219
        buffer: &[INPUT_RECORD],
220
        number_written: &mut u32,
221
    ) -> windows::core::Result<()>;
222
223
    /// Gets the last Windows error code.
224
    ///
225
    /// # Returns
226
    ///
227
    /// The last error code from Windows API
228
    fn get_last_error(&self) -> u32;
229
230
    /// Interrupt every process attached to the caller's console.
231
    ///
232
    /// Sends `CTRL_C_EVENT` to process group 0 so a Ctrl+C relayed from the
233
    /// daemon reaches the child exactly as a focused Ctrl+C would.
234
    /// `CTRL_BREAK_EVENT` is not interchangeable: many programs treat only
235
    /// Ctrl+C as the interrupt (e.g. `ping` stops on Ctrl+C but merely prints
236
    /// statistics on Ctrl+Break). The caller shields itself with the handler
237
    /// from [`Self::install_console_ctrl_handler`], since group 0 signals it too.
238
    /// <https://learn.microsoft.com/en-us/windows/console/generateconsolectrlevent>
239
    ///
240
    /// # Returns
241
    ///
242
    /// Result indicating success or failure of the operation
243
    fn interrupt_console_process_group(&self) -> windows::core::Result<()>;
244
245
    /// Install a console control handler that shields this process from
246
    /// CTRL+C and CTRL+Break.
247
    ///
248
    /// The handler reports both `CTRL_C_EVENT` and `CTRL_BREAK_EVENT` as
249
    /// handled, so neither user-typed nor group-relayed signals terminate the
250
    /// calling process; other control events keep their default handling.
251
    ///
252
    /// # Returns
253
    ///
254
    /// Result indicating success or failure of the operation
255
    fn install_console_ctrl_handler(&self) -> windows::core::Result<()>;
256
257
    /// Get standard output handle
258
    ///
259
    /// # Returns
260
    ///
261
    /// Handle to standard output or error
262
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE>;
263
264
    /// Get console screen buffer information
265
    ///
266
    /// # Arguments
267
    ///
268
    /// * `handle` - Handle to console screen buffer
269
    ///
270
    /// # Returns
271
    ///
272
    /// Console screen buffer information or error
273
    fn get_console_attached_process_count(&self) -> u32;
274
275
    /// Create a new process attached to its own console window.
276
    ///
277
    /// When `with_keyboard_focus` is false the new console window is
278
    /// shown without activation (`STARTF_USESHOWWINDOW` +
279
    /// `SW_SHOWNOACTIVATE`), so the caller retains keyboard focus.
280
    /// Used when the daemon spawns client consoles - otherwise the
281
    /// last-spawned client wins the foreground and Windows refuses to
282
    /// let the daemon steal it back.
283
    ///
284
    /// # Arguments
285
    ///
286
    /// * `application`         - Application name including file extension
287
    /// * `args`                - List of arguments to the application
288
    /// * `with_keyboard_focus` - Whether the new console window should take
289
    ///                           foreground focus when it appears.
290
    ///
291
    /// # Returns
292
    ///
293
    /// Process information if successful, None otherwise
294
1
    fn create_process_with_args(
295
1
        &self,
296
1
        application: &str,
297
1
        args: Vec<String>,
298
1
        with_keyboard_focus: bool,
299
1
    ) -> Option<windows::Win32::System::Threading::PROCESS_INFORMATION> {
300
1
        let command_line = build_command_line(application, &args);
301
1
        let mut startupinfo = build_startupinfo(with_keyboard_focus);
302
1
        let mut process_information = PROCESS_INFORMATION::default();
303
1
        let mut cmd_line = command_line;
304
1
        let command_line_ptr = windows::core::PWSTR(cmd_line.as_mut_ptr());
305
306
1
        match self.create_process_raw(
307
1
            application,
308
1
            command_line_ptr,
309
1
            &mut startupinfo,
310
1
            &mut process_information,
311
1
        ) {
312
1
            Ok(()) => return Some(process_information),
313
0
            Err(_) => return None,
314
        }
315
1
    }
316
317
    /// Create a new process from `OsStr`/`OsString` inputs without lossy
318
    /// UTF-8 conversion.
319
    ///
320
    /// Mirrors [`Self::create_process_with_args`] but preserves the
321
    /// platform-native UTF-16 representation of `application` and `args`.
322
    /// Use this from code that hands paths or user-supplied arguments
323
    /// straight to the spawner (e.g. the platform-trait
324
    /// `ProcessSpawner::spawn`) - non-UTF-8 sequences are passed through
325
    /// to `CreateProcessW` unmodified instead of being replaced with
326
    /// `U+FFFD`.
327
    ///
328
    /// # Arguments
329
    ///
330
    /// * `application`         - Application path or name.
331
    /// * `args`                - Arguments to the application.
332
    /// * `with_keyboard_focus` - Whether the new console window should
333
    ///                           take foreground focus when it appears.
334
    ///
335
    /// # Returns
336
    ///
337
    /// Process information on success, or the originating
338
    /// [`windows::core::Error`] from `CreateProcessW`.
339
0
    fn create_process_with_os_args(
340
0
        &self,
341
0
        application: &OsStr,
342
0
        args: &[OsString],
343
0
        with_keyboard_focus: bool,
344
0
    ) -> windows::core::Result<PROCESS_INFORMATION> {
345
0
        let app_wide = encode_wide_z(application);
346
0
        let mut cmd_line = build_command_line_wide(application, args);
347
0
        let mut startupinfo = build_startupinfo(with_keyboard_focus);
348
0
        let mut process_information = PROCESS_INFORMATION::default();
349
0
        let command_line_ptr = windows::core::PWSTR(cmd_line.as_mut_ptr());
350
351
0
        self.create_process_raw_wide(
352
0
            &app_wide,
353
0
            command_line_ptr,
354
0
            &mut startupinfo,
355
0
            &mut process_information,
356
0
        )?;
357
0
        return Ok(process_information);
358
0
    }
359
360
    /// Low-level `CreateProcessW` call accepting an already-wide,
361
    /// null-terminated application path.
362
    ///
363
    /// # Arguments
364
    ///
365
    /// * `application_wide` - UTF-16, null-terminated application path.
366
    /// * `command_line`     - Mutable UTF-16 command line as `PWSTR`.
367
    /// * `startup_info`     - Startup information structure.
368
    /// * `process_info`     - Output process information.
369
    ///
370
    /// # Returns
371
    ///
372
    /// Result indicating success or failure of the operation.
373
    fn create_process_raw_wide(
374
        &self,
375
        application_wide: &[u16],
376
        command_line: windows::core::PWSTR,
377
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
378
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
379
    ) -> windows::core::Result<()>;
380
381
    /// Low-level process creation API call
382
    ///
383
    /// # Arguments
384
    ///
385
    /// * `application` - Application name
386
    /// * `command_line` - Command line string as PWSTR
387
    /// * `startup_info` - Startup information structure
388
    /// * `process_info` - Process information structure to fill
389
    ///
390
    /// # Returns
391
    ///
392
    /// Result indicating success or failure of the operation
393
    fn create_process_raw(
394
        &self,
395
        application: &str,
396
        command_line: windows::core::PWSTR,
397
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
398
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
399
    ) -> windows::core::Result<()>;
400
401
    /// Get window handle for process ID
402
    ///
403
    /// # Arguments
404
    ///
405
    /// * `process_id` - Process ID to find window for
406
    ///
407
    /// # Returns
408
    ///
409
    /// Window handle for the process
410
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND;
411
412
    /// Gets the console window handle.
413
    ///
414
    /// # Returns
415
    ///
416
    /// Handle to the console window
417
    fn get_console_window(&self) -> HWND;
418
419
    /// Gets the foreground window handle.
420
    ///
421
    /// # Returns
422
    ///
423
    /// Handle to the foreground window
424
    fn get_foreground_window(&self) -> HWND;
425
426
    /// Bring `hwnd` to the top of the z-order.
427
    ///
428
    /// When `with_keyboard_focus` is true the window is also activated and
429
    /// receives keyboard focus. When false the window is raised without
430
    /// activation, avoiding the taskbar flash that would happen if the
431
    /// window is not the current input target.
432
    ///
433
    /// # Arguments
434
    ///
435
    /// * `hwnd`                - Handle to the window to raise.
436
    /// * `with_keyboard_focus` - Whether to activate the window and give
437
    ///                           it keyboard focus.
438
    ///
439
    /// # Returns
440
    ///
441
    /// Result indicating success or failure of the operation
442
    fn bring_window_to_top(
443
        &self,
444
        hwnd: HWND,
445
        with_keyboard_focus: bool,
446
    ) -> windows::core::Result<()>;
447
448
    /// Gets console mode for the specified handle.
449
    ///
450
    /// # Arguments
451
    ///
452
    /// * `handle` - Handle to the console input buffer
453
    ///
454
    /// # Returns
455
    ///
456
    /// Console mode or error
457
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE>;
458
459
    /// Sets console mode for the specified handle.
460
    ///
461
    /// # Arguments
462
    ///
463
    /// * `handle` - Handle to the console input buffer
464
    /// * `mode` - Console mode to set
465
    ///
466
    /// # Returns
467
    ///
468
    /// Result indicating success or failure of the operation
469
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()>;
470
471
    /// Gets the exit code of the specified process.
472
    ///
473
    /// # Arguments
474
    ///
475
    /// * `handle` - Handle to the process
476
    ///
477
    /// # Returns
478
    ///
479
    /// Exit code or error
480
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32>;
481
482
    /// Moves and resizes a window.
483
    ///
484
    /// # Arguments
485
    ///
486
    /// * `hwnd` - Handle to the window
487
    /// * `x` - New x position
488
    /// * `y` - New y position
489
    /// * `width` - New width
490
    /// * `height` - New height
491
    /// * `repaint` - Whether to repaint the window
492
    ///
493
    /// # Returns
494
    ///
495
    /// Result indicating success or failure of the operation
496
    fn move_window(
497
        &self,
498
        hwnd: HWND,
499
        x: i32,
500
        y: i32,
501
        width: i32,
502
        height: i32,
503
        repaint: bool,
504
    ) -> windows::core::Result<()>;
505
506
    /// Gets window placement information.
507
    ///
508
    /// # Arguments
509
    ///
510
    /// * `hwnd` - Handle to the window
511
    ///
512
    /// # Returns
513
    ///
514
    /// Window placement information or error
515
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT>;
516
517
    /// Shows a window in the specified state.
518
    ///
519
    /// # Arguments
520
    ///
521
    /// * `hwnd` - Handle to the window
522
    /// * `cmd_show` - Show command
523
    ///
524
    /// # Returns
525
    ///
526
    /// Result indicating success or failure of the operation
527
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool>;
528
529
    /// Checks if a window handle is valid.
530
    ///
531
    /// # Arguments
532
    ///
533
    /// * `hwnd` - Handle to the window to check
534
    ///
535
    /// # Returns
536
    ///
537
    /// True if the window is valid, false otherwise
538
    fn is_window(&self, hwnd: HWND) -> bool;
539
540
    /// Opens a process with the specified access rights.
541
    ///
542
    /// # Arguments
543
    ///
544
    /// * `access` - Access rights for the process handle
545
    /// * `inherit` - Whether the handle can be inherited
546
    /// * `process_id` - Process ID to open
547
    ///
548
    /// # Returns
549
    ///
550
    /// Process handle or error
551
    fn open_process(
552
        &self,
553
        access: u32,
554
        inherit: bool,
555
        process_id: u32,
556
    ) -> windows::core::Result<HANDLE>;
557
558
    /// Gets system metrics information.
559
    ///
560
    /// # Arguments
561
    ///
562
    /// * `index` - System metric index to retrieve
563
    ///
564
    /// # Returns
565
    ///
566
    /// The requested system metric value
567
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32;
568
569
    /// Sets the process DPI awareness.
570
    ///
571
    /// # Arguments
572
    ///
573
    /// * `value` - DPI awareness value to set
574
    ///
575
    /// # Returns
576
    ///
577
    /// Result indicating success or failure of the operation
578
    fn set_process_dpi_awareness(
579
        &self,
580
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
581
    ) -> windows::core::Result<()>;
582
}
583
584
#[cfg(any(test, feature = "mock"))]
585
impl Clone for MockWindowsApi {
586
0
    fn clone(&self) -> Self {
587
0
        return MockWindowsApi::new();
588
0
    }
589
}
590
591
/// Console control handler that swallows CTRL+C and CTRL+Break.
592
///
593
/// Returning `TRUE` marks the event as handled so it does not terminate this
594
/// process; other control events return `FALSE` to keep their default handling.
595
/// <https://learn.microsoft.com/en-us/windows/console/handlerroutine>
596
#[cfg_attr(coverage_nightly, coverage(off))]
597
unsafe extern "system" fn console_ctrl_handler(ctrl_type: u32) -> BOOL {
598
    return match ctrl_type {
599
        CTRL_C_EVENT | CTRL_BREAK_EVENT => TRUE,
600
        _ => FALSE,
601
    };
602
}
603
604
/// Default implementation of WindowsApi that calls actual Windows APIs.
605
///
606
/// This implementation provides direct access to Windows system APIs and should
607
/// be used in production code. For testing, use the MockWindowsApi instead.
608
#[derive(Clone)]
609
pub struct DefaultWindowsApi;
610
611
#[cfg_attr(coverage_nightly, coverage(off))]
612
impl WindowsApi for DefaultWindowsApi {
613
    fn set_console_title(&self, title: &str) -> windows::core::Result<()> {
614
        return unsafe { SetWindowTextW(GetConsoleWindow(), &HSTRING::from(title)) };
615
    }
616
617
    fn get_console_title(&self, buffer: &mut [u16]) -> i32 {
618
        return unsafe { GetWindowTextW(GetConsoleWindow(), buffer) };
619
    }
620
621
    fn get_os_version(&self) -> String {
622
        return os_info::get().version().to_string();
623
    }
624
625
    fn arrange_console(
626
        &self,
627
        x: i32,
628
        y: i32,
629
        width: i32,
630
        height: i32,
631
    ) -> windows::core::Result<()> {
632
        return unsafe { MoveWindow(GetConsoleWindow(), x, y, width, height, true) };
633
    }
634
635
    fn set_console_text_attribute(
636
        &self,
637
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
638
    ) -> windows::core::Result<()> {
639
        return unsafe { SetConsoleTextAttribute(self.get_stdout_handle()?, attributes) };
640
    }
641
642
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO> {
643
        let mut buffer_info = CONSOLE_SCREEN_BUFFER_INFO::default();
644
        unsafe { GetConsoleScreenBufferInfo(self.get_stdout_handle()?, &mut buffer_info)? };
645
        return Ok(buffer_info);
646
    }
647
648
    fn fill_console_output_attribute(
649
        &self,
650
        attribute: u16,
651
        length: u32,
652
        coord: COORD,
653
    ) -> windows::core::Result<u32> {
654
        let mut number_written = 0u32;
655
        unsafe {
656
            FillConsoleOutputAttribute(
657
                self.get_stdout_handle()?,
658
                attribute,
659
                length,
660
                coord,
661
                &mut number_written,
662
            )?
663
        };
664
        return Ok(number_written);
665
    }
666
667
    fn scroll_console_screen_buffer(
668
        &self,
669
        scroll_rect: SMALL_RECT,
670
        scroll_target: COORD,
671
        fill_char: CHAR_INFO,
672
    ) -> windows::core::Result<()> {
673
        return unsafe {
674
            ScrollConsoleScreenBufferW(
675
                self.get_stdout_handle()?,
676
                &scroll_rect,
677
                None,
678
                scroll_target,
679
                &fill_char,
680
            )
681
        };
682
    }
683
684
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()> {
685
        return unsafe { SetConsoleCursorPosition(self.get_stdout_handle()?, position) };
686
    }
687
688
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE> {
689
        return unsafe { GetStdHandle(handle_type) };
690
    }
691
692
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32> {
693
        let mut number_read = 0u32;
694
        unsafe {
695
            ReadConsoleInputW(
696
                self.get_std_handle(STD_INPUT_HANDLE)?,
697
                buffer,
698
                &mut number_read,
699
            )?
700
        };
701
        return Ok(number_read);
702
    }
703
704
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()> {
705
        return unsafe {
706
            DwmSetWindowAttribute(
707
                GetConsoleWindow(),
708
                DWMWA_BORDER_COLOR,
709
                color as *const COLORREF as *const _,
710
                mem::size_of::<COLORREF>() as u32,
711
            )
712
        };
713
    }
714
715
    fn invalidate_console_window(&self) -> windows::core::Result<()> {
716
        let succeeded = unsafe { InvalidateRect(Some(GetConsoleWindow()), None, false) };
717
        if succeeded.as_bool() {
718
            return Ok(());
719
        }
720
        return Err(windows::core::Error::from_thread());
721
    }
722
723
    fn write_console_input(
724
        &self,
725
        buffer: &[INPUT_RECORD],
726
        number_written: &mut u32,
727
    ) -> windows::core::Result<()> {
728
        unsafe {
729
            windows::Win32::System::Console::WriteConsoleInputW(
730
                GetStdHandle(STD_INPUT_HANDLE)?,
731
                buffer,
732
                number_written,
733
            )?
734
        };
735
        return Ok(());
736
    }
737
738
    fn get_last_error(&self) -> u32 {
739
        return unsafe { windows::Win32::Foundation::GetLastError().0 };
740
    }
741
742
    fn interrupt_console_process_group(&self) -> windows::core::Result<()> {
743
        return unsafe {
744
            windows::Win32::System::Console::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)
745
        };
746
    }
747
748
    fn install_console_ctrl_handler(&self) -> windows::core::Result<()> {
749
        return unsafe { SetConsoleCtrlHandler(Some(console_ctrl_handler), true) };
750
    }
751
752
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE> {
753
        return self.get_std_handle(STD_OUTPUT_HANDLE);
754
    }
755
756
    fn get_console_attached_process_count(&self) -> u32 {
757
        let mut value: [u32; 1] = [0];
758
        unsafe { return GetConsoleProcessList(&mut value) };
759
    }
760
761
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND {
762
        /// Data structure for window search callback
763
        struct WindowSearchData {
764
            /// The process ID we're searching for
765
            target_process_id: u32,
766
            /// Mutable reference to store the found window handle
767
            found_handle: *mut Option<HWND>,
768
        }
769
770
        /// Callback function for finding windows by process ID with proper handle capture
771
        unsafe extern "system" fn find_window_callback_with_capture(
772
            hwnd: HWND,
773
            lparam: LPARAM,
774
        ) -> BOOL {
775
            let search_data = &mut *(lparam.0 as *mut WindowSearchData);
776
            let mut window_process_id: u32 = 0;
777
            GetWindowThreadProcessId(hwnd, Some(&mut window_process_id));
778
779
            if search_data.target_process_id == window_process_id {
780
                // Store the found window handle
781
                *search_data.found_handle = Some(hwnd);
782
                return FALSE; // Stop enumeration
783
            }
784
            return TRUE; // Continue enumeration
785
        }
786
787
        let mut found_handle = None;
788
        let mut search_data = WindowSearchData {
789
            target_process_id: process_id,
790
            found_handle: &mut found_handle,
791
        };
792
793
        loop {
794
            let _ = unsafe {
795
                EnumWindows(
796
                    Some(find_window_callback_with_capture),
797
                    LPARAM(&mut search_data as *mut WindowSearchData as isize),
798
                )
799
            };
800
            if let Some(handle) = found_handle {
801
                return handle;
802
            }
803
        }
804
    }
805
806
    fn create_process_raw(
807
        &self,
808
        application: &str,
809
        command_line: windows::core::PWSTR,
810
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
811
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
812
    ) -> windows::core::Result<()> {
813
        return unsafe {
814
            CreateProcessW(
815
                &HSTRING::from(application),
816
                Some(command_line),
817
                Some(ptr::null_mut()),
818
                Some(ptr::null_mut()),
819
                false,
820
                CREATE_NEW_CONSOLE,
821
                Some(ptr::null_mut()),
822
                PCWSTR::null(),
823
                ptr::addr_of_mut!(*startup_info),
824
                ptr::addr_of_mut!(*process_info),
825
            )
826
        };
827
    }
828
829
    fn create_process_raw_wide(
830
        &self,
831
        application_wide: &[u16],
832
        command_line: windows::core::PWSTR,
833
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
834
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
835
    ) -> windows::core::Result<()> {
836
        let app_pcwstr = if application_wide.is_empty() {
837
            PCWSTR::null()
838
        } else {
839
            PCWSTR(application_wide.as_ptr())
840
        };
841
        return unsafe {
842
            CreateProcessW(
843
                app_pcwstr,
844
                Some(command_line),
845
                Some(ptr::null_mut()),
846
                Some(ptr::null_mut()),
847
                false,
848
                CREATE_NEW_CONSOLE,
849
                Some(ptr::null_mut()),
850
                PCWSTR::null(),
851
                ptr::addr_of_mut!(*startup_info),
852
                ptr::addr_of_mut!(*process_info),
853
            )
854
        };
855
    }
856
857
    fn get_console_window(&self) -> HWND {
858
        return unsafe { GetConsoleWindow() };
859
    }
860
861
    fn get_foreground_window(&self) -> HWND {
862
        return unsafe { GetForegroundWindow() };
863
    }
864
865
    fn bring_window_to_top(
866
        &self,
867
        hwnd: HWND,
868
        with_keyboard_focus: bool,
869
    ) -> windows::core::Result<()> {
870
        if with_keyboard_focus {
871
            return unsafe { BringWindowToTop(hwnd) };
872
        }
873
        // Raise without activation via the HWND_TOPMOST -> HWND_NOTOPMOST
874
        // trick. Synchronous SetWindowPos (no SWP_ASYNCWINDOWPOS) so
875
        // HWND_TOPMOST is applied before we strip it again - otherwise
876
        // rapid invocations from the z-order loop can leave the
877
        // WS_EX_TOPMOST flag set, floating client windows above other
878
        // applications. See https://stackoverflow.com/questions/5257977.
879
        unsafe {
880
            SetWindowPos(
881
                hwnd,
882
                Some(HWND_TOPMOST),
883
                0,
884
                0,
885
                0,
886
                0,
887
                SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
888
            )?;
889
            SetWindowPos(
890
                hwnd,
891
                Some(HWND_NOTOPMOST),
892
                0,
893
                0,
894
                0,
895
                0,
896
                SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
897
            )?;
898
        }
899
        return Ok(());
900
    }
901
902
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE> {
903
        let mut mode = CONSOLE_MODE(0u32);
904
        unsafe { GetConsoleMode(handle, &mut mode)? };
905
        return Ok(mode);
906
    }
907
908
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()> {
909
        return unsafe { SetConsoleMode(handle, mode) };
910
    }
911
912
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32> {
913
        let mut exit_code: u32 = 0;
914
        unsafe { GetExitCodeProcess(handle, &mut exit_code)? };
915
        return Ok(exit_code);
916
    }
917
918
    fn move_window(
919
        &self,
920
        hwnd: HWND,
921
        x: i32,
922
        y: i32,
923
        width: i32,
924
        height: i32,
925
        repaint: bool,
926
    ) -> windows::core::Result<()> {
927
        return unsafe { MoveWindow(hwnd, x, y, width, height, repaint) };
928
    }
929
930
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT> {
931
        let mut placement: WINDOWPLACEMENT = WINDOWPLACEMENT {
932
            length: mem::size_of::<WINDOWPLACEMENT>() as u32,
933
            ..Default::default()
934
        };
935
        unsafe { GetWindowPlacement(hwnd, &mut placement)? };
936
        return Ok(placement);
937
    }
938
939
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool> {
940
        let result = unsafe { ShowWindow(hwnd, cmd_show) };
941
        return Ok(result.as_bool());
942
    }
943
944
    fn is_window(&self, hwnd: HWND) -> bool {
945
        return unsafe { windows::Win32::UI::WindowsAndMessaging::IsWindow(Some(hwnd)).as_bool() };
946
    }
947
948
    fn open_process(
949
        &self,
950
        access: u32,
951
        inherit: bool,
952
        process_id: u32,
953
    ) -> windows::core::Result<HANDLE> {
954
        return unsafe { OpenProcess(PROCESS_ACCESS_RIGHTS(access), inherit, process_id) };
955
    }
956
957
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32 {
958
        return unsafe { windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics(index) };
959
    }
960
961
    fn set_process_dpi_awareness(
962
        &self,
963
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
964
    ) -> windows::core::Result<()> {
965
        return unsafe { windows::Win32::UI::HiDpi::SetProcessDpiAwareness(value) };
966
    }
967
}
968
969
/// u16 representation of a [KEY_EVENT][1].
970
///
971
/// For some reason the public [KEY_EVENT][1] constant is a u32
972
/// while the [INPUT_RECORD][2].`EventType` is u16...
973
///
974
/// [1]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/constant.KEY_EVENT.html
975
/// [2]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/struct.INPUT_RECORD.html
976
pub const KEY_EVENT: u16 = KEY_EVENT_U32 as u16;
977
978
/// Build a `STARTUPINFOW` for [`WindowsApi::create_process_with_args`].
979
///
980
/// When `with_keyboard_focus` is false, `STARTF_USESHOWWINDOW` and
981
/// `SW_SHOWNOACTIVATE` are populated so the new console appears without
982
/// stealing foreground focus. Otherwise the struct is left at its default
983
/// (the new process picks its own show-window behaviour).
984
///
985
/// # Arguments
986
///
987
/// * `with_keyboard_focus` - Whether the spawned process is allowed to take
988
///                           foreground focus when its console appears.
989
///
990
/// # Returns
991
///
992
/// A `STARTUPINFOW` with `cb` set and, when applicable, the no-activate
993
/// show-window flags applied.
994
3
pub(crate) fn build_startupinfo(with_keyboard_focus: bool) -> STARTUPINFOW {
995
3
    let mut startupinfo = STARTUPINFOW {
996
3
        cb: mem::size_of::<STARTUPINFOW>() as u32,
997
3
        ..Default::default()
998
3
    };
999
3
    if !with_keyboard_focus {
1000
1
        startupinfo.dwFlags = STARTF_USESHOWWINDOW;
1001
1
        startupinfo.wShowWindow = SW_SHOWNOACTIVATE.0 as u16;
1002
2
    }
1003
3
    return startupinfo;
1004
3
}
1005
1006
/// Build command line string for Windows process creation
1007
///
1008
/// # Arguments
1009
///
1010
/// * `application` - Application name including file extension
1011
/// * `args` - List of arguments to the application
1012
///
1013
/// # Returns
1014
///
1015
/// UTF-16 encoded command line with proper quoting
1016
///
1017
/// # Examples
1018
///
1019
/// ```
1020
/// use cssh_rs_platform_windows::build_command_line;
1021
///
1022
/// let cmd_line = build_command_line("cmd.exe", &["arg1".to_string(), "arg2".to_string()]);
1023
/// // Returns UTF-16 encoded: "cmd.exe" "arg1" "arg2"\0
1024
/// ```
1025
5
pub fn build_command_line(application: &str, args: &[String]) -> Vec<u16> {
1026
5
    let mut cmd: Vec<u16> = Vec::new();
1027
5
    cmd.push(b'"' as u16);
1028
5
    cmd.extend(OsString::from(application).encode_wide());
1029
5
    cmd.push(b'"' as u16);
1030
1031
7
    for arg in 
args5
{
1032
7
        cmd.push(' ' as u16);
1033
7
        cmd.push(b'"' as u16);
1034
7
        cmd.extend(OsString::from(arg).encode_wide());
1035
7
        cmd.push(b'"' as u16);
1036
7
    }
1037
5
    cmd.push(0); // add null terminator
1038
1039
5
    return cmd;
1040
5
}
1041
1042
/// Build a UTF-16, null-terminated command line directly from `OsStr`
1043
/// inputs.
1044
///
1045
/// Mirrors [`build_command_line`] but skips the `&str`/`String` round-trip
1046
/// so non-UTF-8 byte sequences (typical for paths and user-supplied
1047
/// arguments on Windows) survive intact.
1048
///
1049
/// # Arguments
1050
///
1051
/// * `application` - Application path or name.
1052
/// * `args`        - Arguments to the application.
1053
///
1054
/// # Returns
1055
///
1056
/// UTF-16 encoded command line with proper quoting.
1057
3
pub fn build_command_line_wide(application: &OsStr, args: &[OsString]) -> Vec<u16> {
1058
3
    let mut cmd: Vec<u16> = Vec::new();
1059
3
    cmd.push(b'"' as u16);
1060
3
    cmd.extend(application.encode_wide());
1061
3
    cmd.push(b'"' as u16);
1062
1063
3
    for arg in args {
1064
3
        cmd.push(' ' as u16);
1065
3
        cmd.push(b'"' as u16);
1066
3
        cmd.extend(arg.encode_wide());
1067
3
        cmd.push(b'"' as u16);
1068
3
    }
1069
3
    cmd.push(0);
1070
1071
3
    return cmd;
1072
3
}
1073
1074
/// UTF-16 encode `s` with a trailing null terminator.
1075
///
1076
/// # Arguments
1077
///
1078
/// * `s` - String to encode.
1079
///
1080
/// # Returns
1081
///
1082
/// UTF-16 encoded buffer suitable for passing to wide-string Win32 APIs
1083
/// such as `CreateProcessW`'s `lpApplicationName`.
1084
2
pub(crate) fn encode_wide_z(s: &OsStr) -> Vec<u16> {
1085
2
    let mut out: Vec<u16> = s.encode_wide().collect();
1086
2
    out.push(0);
1087
2
    return out;
1088
2
}
1089
1090
/// Sets the back- and foreground color of the current console window using the provided API.
1091
///
1092
/// # Arguments
1093
///
1094
/// * `api` - The Windows API implementation to use.
1095
/// * `color` - The color value describing the back- and foreground color.
1096
///
1097
/// # Examples
1098
///
1099
/// ```no_run
1100
/// use cssh_rs_platform_windows::{set_console_color, DefaultWindowsApi};
1101
/// use windows::Win32::System::Console::CONSOLE_CHARACTER_ATTRIBUTES;
1102
///
1103
/// let api = DefaultWindowsApi;
1104
/// set_console_color(&api, CONSOLE_CHARACTER_ATTRIBUTES(0x0F));
1105
/// ```
1106
8
pub fn set_console_color(api: &dyn WindowsApi, color: CONSOLE_CHARACTER_ATTRIBUTES) {
1107
8
    api.set_console_text_attribute(color).unwrap();
1108
8
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
1109
    // FillConsoleOutputAttribute continues into successive rows when the
1110
    // length extends past the end of the row, so a single call from (0,0)
1111
    // recolors the entire buffer in one LPC roundtrip.
1112
8
    let width: u32 = buffer_info.dwSize.X.try_into().unwrap();
1113
8
    let height: u32 = buffer_info.dwSize.Y.try_into().unwrap();
1114
8
    api.fill_console_output_attribute(color.0, width * height, COORD { X: 0, Y: 0 })
1115
8
        .unwrap();
1116
    // The console client area can contain a sub-cell-sized pixel sliver at
1117
    // the right and/or bottom edge when the OS window pixel dimensions do
1118
    // not divide evenly into the cell grid (cssh-rs clients are sized in
1119
    // pixels via MoveWindow). Those slivers are not backed by any buffer
1120
    // cell, so the fill above cannot reach them, and conhost does not
1121
    // repaint them on an attribute-only update - they keep the old color
1122
    // until something triggers a WM_PAINT (user double-clicking is one
1123
    // such trigger).
1124
8
    if let Err(
err2
) = api.invalidate_console_window() {
1125
2
        warn!("Failed to invalidate console window after recolor: {}", err);
1126
6
    }
1127
8
}
1128
1129
/// Empties the console screen output buffer of the current console window using the provided API.
1130
///
1131
/// # Arguments
1132
///
1133
/// * `api` - The Windows API implementation to use.
1134
///
1135
/// # Examples
1136
///
1137
/// ```no_run
1138
/// use cssh_rs_platform_windows::{clear_screen, DefaultWindowsApi};
1139
///
1140
/// let api = DefaultWindowsApi;
1141
/// clear_screen(&api);
1142
/// ```
1143
7
pub fn clear_screen(api: &dyn WindowsApi) {
1144
7
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
1145
7
    let scroll_rect = SMALL_RECT {
1146
7
        Left: 0,
1147
7
        Top: 0,
1148
7
        Right: buffer_info.dwSize.X,
1149
7
        Bottom: buffer_info.dwSize.Y,
1150
7
    };
1151
7
    let scroll_target = COORD {
1152
7
        X: buffer_info.dwSize.X,
1153
7
        Y: 0 - buffer_info.dwSize.Y,
1154
7
    };
1155
7
    let mut char_info = CHAR_INFO::default();
1156
7
    char_info.Char.UnicodeChar = ' ' as u16;
1157
7
    char_info.Attributes = buffer_info.wAttributes.0;
1158
1159
7
    api.scroll_console_screen_buffer(scroll_rect, scroll_target, char_info)
1160
7
        .unwrap();
1161
1162
7
    let cursor_position = COORD { X: 0, Y: 0 };
1163
7
    api.set_console_cursor_position(cursor_position).unwrap();
1164
7
}
1165
1166
/// Sets the border color of the current console window using the provided APIs.
1167
///
1168
/// Windows10 does not support this.
1169
///
1170
/// # Arguments
1171
///
1172
/// * `api` - The Windows API implementation;
1173
/// * `color` - RGB [COLORREF][1] to set as border color.
1174
///
1175
/// # Examples
1176
///
1177
/// ```no_run
1178
/// use cssh_rs_platform_windows::{set_console_border_color, DefaultWindowsApi};
1179
/// use windows::Win32::Foundation::COLORREF;
1180
///
1181
/// set_console_border_color(&DefaultWindowsApi, COLORREF(0x001A2B3C));
1182
/// ```
1183
///
1184
/// [1]: https://learn.microsoft.com/en-us/windows/win32/gdi/colorref
1185
3
pub fn set_console_border_color(api: &dyn WindowsApi, color: COLORREF) {
1186
3
    if !is_windows_10(api) {
1187
2
        api.set_console_border_color(&color).unwrap();
1188
2
    
}1
1189
3
}
1190
1191
/// Converts a UTF-16 buffer to a Rust String, filtering out null characters.
1192
///
1193
/// # Arguments
1194
///
1195
/// * `buffer` - The UTF-16 buffer to convert.
1196
///
1197
/// # Returns
1198
///
1199
/// The converted string.
1200
///
1201
/// # Examples
1202
///
1203
/// ```
1204
/// use cssh_rs_platform_windows::utf16_buffer_to_string;
1205
///
1206
/// let utf16_data = vec![72, 101, 108, 108, 111, 0]; // "Hello" + null terminator
1207
/// let result = utf16_buffer_to_string(&utf16_data);
1208
/// assert_eq!(result, "Hello");
1209
/// ```
1210
6
pub fn utf16_buffer_to_string(buffer: &[u16]) -> String {
1211
6
    let vec: Vec<u16> = buffer
1212
6
        .iter()
1213
6
        .copied()
1214
55
        .
filter6
(|val| return *val != 0u16)
1215
6
        .collect();
1216
6
    return String::from_utf16(&vec).unwrap_or_else(|err| 
{0
1217
0
        error!("{}", err);
1218
0
        panic!("Failed to convert UTF-16 buffer to string, invalid utf16")
1219
    });
1220
6
}
1221
1222
/// Returns the title of the current console window using the provided API.
1223
///
1224
/// # Arguments
1225
///
1226
/// * `api` - The Windows API implementation to use.
1227
///
1228
/// # Returns
1229
///
1230
/// The title of the current console window.
1231
///
1232
/// # Examples
1233
///
1234
/// ```no_run
1235
/// use cssh_rs_platform_windows::{get_console_title, DefaultWindowsApi};
1236
///
1237
/// let title = get_console_title(&DefaultWindowsApi);
1238
/// println!("Console title: {}", title);
1239
/// ```
1240
0
pub fn get_console_title(api: &dyn WindowsApi) -> String {
1241
0
    let mut title: [u16; MAX_WINDOW_TITLE_LENGTH] = [0; MAX_WINDOW_TITLE_LENGTH];
1242
0
    api.get_console_title(&mut title);
1243
0
    return utf16_buffer_to_string(&title);
1244
0
}
1245
1246
/// Returns a [HANDLE] to the requested [STD_HANDLE] of the current process.
1247
///
1248
/// # Arguments
1249
///
1250
/// * `nstdhandle` - The standard handle to retrieve.
1251
///                  Either [STD_INPUT_HANDLE] or [STD_OUTPUT_HANDLE].
1252
///
1253
/// # Returns
1254
///
1255
/// The [HANDLE] to the requested [STD_HANDLE].
1256
#[cfg_attr(coverage_nightly, coverage(off))]
1257
fn get_std_handle(nstdhandle: STD_HANDLE) -> HANDLE {
1258
    return unsafe {
1259
        GetStdHandle(nstdhandle)
1260
            .unwrap_or_else(|_| panic!("Failed to retrieve standard handle: {nstdhandle:?}"))
1261
    };
1262
}
1263
1264
/// Returns a [HANDLE] to the [STD_INPUT_HANDLE] of the current process.
1265
///
1266
/// # Returns
1267
///
1268
/// Handle to the standard input of the current process.
1269
///
1270
/// # Examples
1271
///
1272
/// ```no_run
1273
/// use cssh_rs_platform_windows::get_console_input_buffer;
1274
///
1275
/// let input_handle = get_console_input_buffer();
1276
/// ```
1277
#[cfg_attr(coverage_nightly, coverage(off))]
1278
pub fn get_console_input_buffer() -> HANDLE {
1279
    return get_std_handle(STD_INPUT_HANDLE);
1280
}
1281
1282
/// Returns a [HANDLE] to the [STD_OUTPUT_HANDLE] of the current process.
1283
///
1284
/// # Returns
1285
///
1286
/// Handle to the standard output of the current process.
1287
///
1288
/// # Examples
1289
///
1290
/// ```no_run
1291
/// use cssh_rs_platform_windows::get_console_output_buffer;
1292
///
1293
/// let output_handle = get_console_output_buffer();
1294
/// ```
1295
#[cfg_attr(coverage_nightly, coverage(off))]
1296
pub fn get_console_output_buffer() -> HANDLE {
1297
    return get_std_handle(STD_OUTPUT_HANDLE);
1298
}
1299
1300
/// Returns a single [INPUT_RECORD] read from the current process stdinput using the provided API.
1301
///
1302
/// Blocks until 1 record was read.
1303
///
1304
/// # Arguments
1305
///
1306
/// * `api` - The Windows API implementation to use.
1307
///
1308
/// # Returns
1309
///
1310
/// A single INPUT_RECORD that was read.
1311
///
1312
/// # Examples
1313
///
1314
/// ```no_run
1315
/// use cssh_rs_platform_windows::{read_console_input, DefaultWindowsApi};
1316
///
1317
/// let api = DefaultWindowsApi;
1318
/// let input_record = read_console_input(&api);
1319
/// ```
1320
5
pub fn read_console_input(api: &dyn WindowsApi) -> INPUT_RECORD {
1321
    const NB_EVENTS: usize = 1;
1322
5
    let mut input_buffer: [INPUT_RECORD; NB_EVENTS] = [INPUT_RECORD::default(); NB_EVENTS];
1323
    loop {
1324
6
        let number_of_events_read = api
1325
6
            .read_console_input(&mut input_buffer)
1326
6
            .expect("Failed to read console input");
1327
6
        if number_of_events_read == NB_EVENTS as u32 {
1328
5
            break;
1329
1
        }
1330
    }
1331
5
    return input_buffer[0];
1332
5
}
1333
1334
/// Returns a single [INPUT_RECORD_0] where `EventType` == [`KEY_EVENT`] using the provided API.
1335
///
1336
/// Blocks until 1 key event record was read.
1337
///
1338
/// # Arguments
1339
///
1340
/// * `api` - The Windows API implementation to use.
1341
///
1342
/// # Returns
1343
///
1344
/// A single INPUT_RECORD_0 with EventType == KEY_EVENT.
1345
///
1346
/// # Examples
1347
///
1348
/// ```no_run
1349
/// use cssh_rs_platform_windows::{read_keyboard_input, DefaultWindowsApi};
1350
///
1351
/// let api = DefaultWindowsApi;
1352
/// let key_event = read_keyboard_input(&api);
1353
/// ```
1354
1
pub fn read_keyboard_input(api: &dyn WindowsApi) -> INPUT_RECORD_0 {
1355
    loop {
1356
2
        let input_record = read_console_input(api);
1357
2
        match input_record.EventType {
1358
            KEY_EVENT => {
1359
1
                return input_record.Event;
1360
            }
1361
            _ => {
1362
1
                continue;
1363
            }
1364
        }
1365
    }
1366
1
}
1367
1368
/// Changes size and position of the current console window using the provided API.
1369
///
1370
/// # Arguments
1371
///
1372
/// * `api` - The Windows API implementation to use.
1373
/// * `x`       - The x coordinate to move the window to.
1374
///               From the top left corner of the screen.
1375
/// * `y`       - The y coordinate to move the window to.
1376
///               From the top left corner of the screen.
1377
/// * `width`   - The width in pixels to resize the window to.
1378
///               In logical scaling.
1379
/// * `height`  - The height in pixels to resize the window to.
1380
///               In logical scaling.
1381
///
1382
/// # Examples
1383
///
1384
/// ```no_run
1385
/// use cssh_rs_platform_windows::{arrange_console, DefaultWindowsApi};
1386
///
1387
/// let api = DefaultWindowsApi;
1388
/// arrange_console(&api, 100, 100, 800, 600);
1389
/// ```
1390
0
pub fn arrange_console(api: &dyn WindowsApi, x: i32, y: i32, width: i32, height: i32) {
1391
    // FIXME: sometimes a daemon or client console isn't being arrange correctly
1392
    // when this simply retrying doesn't solve the issue. Maybe it has something to do
1393
    // with DPI awareness => https://docs.rs/embed-manifest/latest/embed_manifest/
1394
0
    api.arrange_console(x, y, width, height).unwrap();
1395
0
}
1396
1397
/// Detects if the current windows installation is Windows 10 or not using the provided API.
1398
///
1399
/// Uses the os version, Windows 10 is < `10._.22000`. Windows 11 started with build 22000.
1400
///
1401
/// # Arguments
1402
///
1403
/// * `api` - The Windows API implementation to use.
1404
///
1405
/// # Returns
1406
///
1407
/// Whether the current windows installation is Windows 10 or not.
1408
///
1409
/// # Examples
1410
///
1411
/// ```no_run
1412
/// use cssh_rs_platform_windows::{is_windows_10, DefaultWindowsApi};
1413
///
1414
/// if is_windows_10(&DefaultWindowsApi) {
1415
///     println!("Running on Windows 10");
1416
/// } else {
1417
///     println!("Running on Windows 11 or newer");
1418
/// }
1419
/// ```
1420
10
pub fn is_windows_10(api: &dyn WindowsApi) -> bool {
1421
10
    let version = api.get_os_version();
1422
10
    let mut iter = version.split('.');
1423
10
    let (major, _, build) = (
1424
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1425
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1426
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1427
10
    );
1428
10
    return major < 10 || (
major == 109
&&
build < 220007
);
1429
10
}
1430
1431
#[cfg(test)]
1432
#[path = "tests/test_api.rs"]
1433
mod test_mod;